package com.dashan.backend.geosearch;

import static com.dashan.backend.geosearch.GeoMath.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.dashan.backend.DashanException;
import com.dashan.backend.datastore.DsPosition;
import com.dashan.backend.datastore.PositionHistoryDao;

public class GeoSearch {
    
    public static final int START_SEARCH_LEVEL = 16;
    public static final int MAX_SEARCH_LEVEL = 6;
    
    public static class GeoComparator implements Comparator<DsPosition> {
        private double lon;
        private double lat;
        
        public GeoComparator(double lon, double lat) {
            this.lon = lon;
            this.lat = lat;
        }
        
        @Override
        public int compare(DsPosition o1, DsPosition o2) {
            double d1 = getMapDistanceByLonLat(lon, lat, o1.getLongitude(), o1.getLatitude());
            double d2 = getMapDistanceByLonLat(lon, lat, o2.getLongitude(), o2.getLatitude());
            
            return Double.compare(d1, d2);
        }
    }
    public static List<DsPosition> boundingBoxSearch(BoundingBox box, boolean currentOnly) 
        throws DashanException {
        
        GeoCell[] coveringCells = box.getCoveringCells();
        
        List<DsPosition> res = new ArrayList<DsPosition>();
        PositionHistoryDao dao = new PositionHistoryDao();
        for (GeoCell c : coveringCells) {
            List<DsPosition> r = dao.getPositionInCell(c, currentOnly);
            for (DsPosition p : r) {
                if (box.isInside(p.getLongitude(), p.getLatitude())) {
                    res.add(p);
                }
            }
        }
        dao.commit();
        
        return res;
    }
    
    public static List<DsPosition> nearestNSearch(double lon, double lat, 
            int n, boolean currentOnly) throws DashanException {
        
        // Use a cell to get search box size.
        GeoCell center = new GeoCell(lon, lat);
        
        int level = START_SEARCH_LEVEL;
        List<DsPosition> res = null;
        while (level > MAX_SEARCH_LEVEL) {
            center = center.castToBiggerCell(level);
            double range = center.getLatSpan() / 2;
            BoundingBox box = new BoundingBox(lat + range , lat - range, lon - range, lon + range);
            res = boundingBoxSearch(box, currentOnly);
            if (res.size() >= n) {
                break;
            }
            level--;
        }
        
        // Sort result.
        Collections.sort(res, new GeoComparator(lon, lat));
        if (n < res.size()) {
            res = res.subList(0, n);
        }
        return res;
    }
    
    public static List<DsPosition> radiusSearch(double lon, double lat, double r,
            boolean currentOnly) throws DashanException {
        double latSpan = getLatSpanByDistance(r);
        double lonSpan = getLonSpanByDistance(r, lat);
        
        
        BoundingBox box = new BoundingBox(lat + latSpan, lat - latSpan, 
                                          lon - lonSpan, lon + lonSpan);
        
        return boundingBoxSearch(box, currentOnly);
    }
}
